# -*- coding: utf-8 -*-
# License: GPL V3
# 2009-02-13 Code reorganization, remove camel-naming 
# breaks interfaces

# Interface version
__VERSION__ = "2.0"

# What needs to be done
# 1. confusion between position in tile and pixel units ends - unique interface for sprites and tiles - done 
# 2. everything that has a position uses pygame.rect and a pygame style interface - done, see 1
# 3. A*Star needs more parameters and has to be object oriented
# 4. Terrain cost in tiles
# 5. Build a module folder and export proper namespaces - done

import pygame
import math
from tuplevector import *
import unit


class ZBuffer(object):
    """
    Class for z-Buffer bookkeeping
    A z-Buffer is a two-dimensional list containing links to all objects in this depth, e.g.:
    _zbuffer[10] is a list of all objects in depth 10
    Objects have to register / unregister
    """
    def __init__(self, maxdepth):
        self._maxdepth = maxdepth
        self._zbuffer = []
        for z in range(0,self._maxdepth):
            self._zbuffer.append( [] )
    def register (self, obj, depth):
        "Register a object in depth"
        self._zbuffer[depth].append(obj)
    def unregister (self, obj, depth):
        "Remove a object from depth-buffer"
        self._zbuffer[depth].remove(obj)
    def get_zbuffer_for_depth (self, depth):
        return self._zbuffer[depth]


class Tile(object):    
    """
    A Tile
    """
    def __init__(self, tile_size=(1,1)):
        self.rect = unit.Tilerect (tile_size)
        self._cost = 0                      # Terrain cost, needed at a*
        self._parent = None                 # tmp. for astar 
        self._zdepth = 0                    # z-buffer depth 
    def __repr__(self):
        return "<Tile:"+str(self.rect)+">"
    def check_pixel (self, pixel_point):
        "true if tile contains pixel_point"
        return self.rect.collidepoint (pixel_point)
    def set_zdepth (self, zd): self._zdepth = zd
    def get_zdepth (self): return self._zdepth
   
class Grid(object):
    def __init__(self, tOriginPi, WTi, HTi, WTilePi, HTilePi):
        self.WTi, self.HTi = WTi, HTi
        self.WTilePi, self.HTilePi = WTilePi, HTilePi
        self.tOriginPi = tOriginPi
        self.tiles = []
        self._tagregister = {}
        self.create()
        self._zBuffer = None
    def get_zbuffer (self): 
        if self.has_zbuffer(): 
            return self._zBuffer
        else:
            return None
    def enable_zbuffer (self):
        self._zBuffer = ZBuffer (self.HTi)
    def disable_zbuffer (self):
        del (self._zBuffer)
    def has_zbuffer (self):
        if hasattr (self,"_zBuffer"):
            return True
        return False
    zbuffer = property (get_zbuffer)
    def register (self, obj, tag):
        "Registers tag for object and object for tag"
        try:
            self._tagregister[tag].append(obj)
        except KeyError:
            self._tagregister[tag] = [] 
            self._tagregister[tag].append(obj)
        try:
            self._tagregister[obj].append(tag)
        except KeyError:
            self._tagregister[obj] = [] 
            self._tagregister[obj].append(tag)
        return True
    def unregister (self, obj, tag):
        try:   
            self._tagregister[tag].remove(obj)
            self._tagregister[obj].remove(tag)
        except (KeyError, ValueError):
            return False
        return True
    def flush_tag (self, tag):
        """ clear tag from all objects and flush register
            warning: only cleans for this tag, not all occurencies in objects
            use flush_register_by_tag instead 
        """
        try: 
            del (self._tagregister[tag])
        except KeyError: 
            return False
        return True
    def flush_register_by_tag (self, tag):
        try:
            for obj in self.get_objs_for_tag(tag):
                self._tagregister[obj].remove(tag)
                self.flush_tag (tag)
        except:
            return False
        return True
    def get_objs_for_tag (self, tag):
        try:
            return self._tagregister[tag]
        except KeyError:
            return []
    def has_tag (self, obj, tag):
        try:
            if tag in self._tagregister[obj]:
                return True
        except KeyError:
            return False
        return False
    def create(self):
        for h in range(0,self.HTi):
            zdepth = h
            for w in range(0,self.WTi):
                self.tiles.append(Tile((self.WTilePi, self.HTilePi)))
                self.tiles[-1].rect.topleft = (self.tOriginPi[0] + (w * self.WTilePi), self.tOriginPi[1] + (h * self.HTilePi))
                self.tiles[-1].rect.size = (self.WTilePi, self.HTilePi)
                self.tiles[-1].set_zdepth (zdepth)
    def get_tile_at_tilepos (self, tPosTi):
        "return Tile fselfor Tile pos"
        XTi, YTi = tPosTi
        if XTi + (YTi * self.WTi) >= 0 and XTi + (YTi * self.WTi) < len(self.tiles):
            return self.tiles[XTi + (YTi * self.WTi)]
        return None 
    def get_tile_at_pixelpos (self, tPosPi):
        "return tile for grid-related-pixel pos (not SCREEN-related)"
        tPosPi = (abs(tPosPi[0]), abs(tPosPi[1]))
        if tPosPi[0]<0 or tPosPi[0] > (self.WTilePi * self.WTi): return None
        if tPosPi[1]<0 or tPosPi[1] > (self.HTilePi * self.HTi): return None
        return self.get_tile_at_tilepos ((int(tPosPi[0] / self.WTilePi), int(tPosPi[1] / self.HTilePi)))
    def get_tile_distance (self, startTile, destTile):
        "return number of tiles between StartTile and DestTile (unit tiles)"
        return vLength (vSub (destTile.rect.tile_topleft, startTile.rect.tile_topleft))
    def get_neighbour_tiles (self, tile):
        "returns a list of all tiles around the selected one"
        if tile == None: return
        x,y = tile.rect.tile_topleft
        ret = []
        t = self.get_tile_at_tilepos((x-1,y-1))
        if t != None: ret.append(t)
        t = self.get_tile_at_tilepos((x,y-1))
        if t != None: ret.append(t)
        t = self.get_tile_at_tilepos((x+1,y-1))
        if t != None: ret.append(t)
        t = self.get_tile_at_tilepos((x-1,y))
        if t != None: ret.append(t)
        t = self.get_tile_at_tilepos((x+1,y))
        if t != None: ret.append(t)
        t = self.get_tile_at_tilepos((x-1,y+1))
        if t != None: ret.append(t)
        t = self.get_tile_at_tilepos((x,y+1))
        if t != None: ret.append(t)
        t = self.get_tile_at_tilepos((x+1,y+1))
        if t != None: ret.append(t)
        return ret
    def get_heading_to_target (self, tStartPosPi, destTile):
        """
        calculates and returns the angle between a vector (0,1) and (endtilepos-x - startpos-x, ...)
        """
        #if destTile == None: return 0
        tEndPosPi = destTile.rect.center
        if tEndPosPi[0] > tStartPosPi[0]:
            d = vSub (tEndPosPi, tStartPosPi)
            heading = vAngle ((0,1),vNormalize(d)) 
        else:
            d = vSub (tStartPosPi, tEndPosPi)
            heading = vAngle ((0,1),vNormalize(d)) + 180  
        return heading
    

    # Special functions ??
    def screen_to_grid (self, tScreenPi):
        self.s2g (tScreenPi)
    def grid_to_screen (self, tGridPi):
        self.g2s (tGridPi)
    def s2g (self, tScreenPi):
        " Convert Screen in Grid Coordinates (Subtract offset)"
        return vSub (self.tOriginPi, tScreenPi)
    def g2s (self, tGridPi):
        " Convert Grid in Screen Coordinates (add offset)"
        return vAdd (self.tOriginPi, tGridPi)
    def print_tag_register (self, mintags=0):
        print "START Grid Tag register Statistics"
        if mintags > 0:
            print "Output register with",mintags,"entries"
        for l in self._tagregister:
            if len(self._tagregister[l]) >= mintags:
                print l, ":", len(self._tagregister[l]),"tags:", self._tagregister[l]
        print "END Grid Tag register Statistics"
    def find_nearest_tile_by_tag (self, startTile, tag):
        """ returns a closest tile (by distance in tile units) of type "tag" """
        # search strategy:
        # get a list of all objects that are marked as "tag"
        # seperates tiles
        # calculate their distance from "tPosTi" -> if they have it!
        candidates = [c for c in self.getObjectsByTag(tag) if isinstance (c, cTile)] 
        if candidates == []: return None
        dist = [(self.getTileDistance(startTile, c), c) for c in candidates] 
        dist.sort()
        return dist[0][1]      
    def scrollUp (self, deltaPi): 
        res = vAdd(self.tOriginPi, (0,deltaPi))
        if res[1] <= 0:
            self.tOriginPi = res 
    def scrollDown (self, deltaPi, screenHeight):
        res = vSub(self.tOriginPi, (0,deltaPi))
        if res[1] >= 0 - (self.HTi * self.HTilePi) + screenHeight:
            self.tOriginPi = res
    def scrollLeft (self, deltaPi):
        res = vAdd(self.tOriginPi, (deltaPi,0)) 
        if res[0] <= 0:
            self.tOriginPi = res
    def scrollRight (self, deltaPi, screenWidth): 
        res = vSub(self.tOriginPi, (deltaPi,0))
        if res[0] >= 0-(self.WTi * self.WTilePi) + screenWidth:
            self.tOriginPi = res

    
# Profiling for this module
def profileme ():
    g = cGrid((0,0), 100, 100, 10,10)
    #findpath (g, g.getTile((1,1)), g.getTile((99,99)) )
    man = cSprite((0,0), None)
    tile = cTile ()
    for i in xrange (0,10000):
        g.register (man, tile)
        g.unregister (man, tile)

def profile ():
    import hotshot
    prof = hotshot.Profile("hotshot_stats")
    prof.runcall(profileme)
    prof.close()

    # print stats
    from hotshot import stats
    s = stats.load("hotshot_stats")
    s.sort_stats("time").print_stats()

    

    
if __name__ == "__main__":
    g = Grid ((0,0), 10, 10, 15, 15)
    print "0. Set grid to origin 0,0, 10x10 tiles, 15pixel wxh",g
    x = g.get_tile_at_tilepos((5,5)) 
    print "1. Get Tile x at 5/5 (ti)", x
    y = g.get_tile_at_pixelpos((115,115))
    print "2. Get Tile y at 115x115 (pi)", y
    print "2b. Find neighbors of y:", g.get_neighbour_tiles(y)
    d = g.get_tile_distance(x, y)
    print "3. Distance between x and y in tiles", d
    head = g.get_heading_to_target((0,0), y)
    print "4. Heading from (0,0) to y in degree", head